﻿using System;

namespace PhenomMsrTweaker
{
	/// <summary>
	/// Represents the interesting MSR settings of a CPU core's P-state.
	/// </summary>
	public struct PStateMsr
	{
		/// <summary>
		/// Core multiplicator (4, 4.5, 5, ..., 31.5).
		/// </summary>
		public double Multi { get; set; }

		/// <summary>
		/// Core voltage ID (0.0125 ... 1.55V).
		/// </summary>
		public double CpuVid { get; set; }

		/// <summary>
		/// Northbridge voltage ID (0.0125 ... 1.55V).
		/// </summary>
		public double NbVid { get; set; }


		/// <summary>
		/// Loads a core's P-state.
		/// </summary>
		public static PStateMsr Load(int pStateIndex, int coreIndex)
		{
			if (pStateIndex < 0 || pStateIndex > 4)
				throw new ArgumentOutOfRangeException("pStateIndex");

			uint lower = (uint)(Program.Ols.ReadMsr(0xC0010064u + (uint)pStateIndex, coreIndex) & 0xFFFFFFFFu);
			return Decode(lower);
		}


		/// <summary>
		/// Decodes the settings from the 32 lower bits of a MSR.
		/// </summary>
		public static PStateMsr Decode(uint value)
		{
			uint cpuFid = (value >>  0) & 0x3F;
			uint cpuDid = (value >>  6) & 0x07;
			uint cpuVid = (value >>  9) & 0x7F;
			uint  nbVid = (value >> 25) & 0x7F;

			var msr = new PStateMsr()
			{
				Multi = 0.5 * ((16 + cpuFid) >> (int)cpuDid), // [(16 + fid) / 2^did)] / 2
				CpuVid = 1.55 - 0.0125 * cpuVid,
				NbVid = 1.55 - 0.0125 * nbVid
			};

			return msr;
		}

		/// <summary>
		/// Encodes the settings into the 32 lower bits of a MSR.
		/// </summary>
		public uint Encode()
		{
			if (Multi < 4 || Multi > 31.5)
				throw new ArgumentOutOfRangeException("Multi");

			if (CpuVid <= 0 || CpuVid > 1.55)
				throw new ArgumentOutOfRangeException("CpuVid");

			if (NbVid <= 0 || NbVid > 1.55)
				throw new ArgumentOutOfRangeException("NbVid");

			uint cpuFid, cpuDid;
			uint doubleMulti = (uint)Math.Round(2.0 * Multi); // the actual multis are relative to half the reference clock (usually 100 MHz)
			if (doubleMulti < 16)
			{
				cpuDid = 1; // divisor 2
				cpuFid = 2 * doubleMulti - 16;
			}
			else
			{
				cpuDid = 0; // divisor 1
				cpuFid = doubleMulti - 16;
			}

			uint cpuVid = (uint)Math.Round((1.55 - CpuVid) / 0.0125);
			uint nbVid = (uint)Math.Round((1.55 - NbVid) / 0.0125);

			return (nbVid << 25) | (cpuVid << 9) | (cpuDid << 6) | cpuFid;
		}
	}
}
